package lxs.swift.collector;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.*;
import java.util.*;


public class ImMut<T> {
    public final T value;
    private static final List<String> keyWords = Collections.unmodifiableList(Arrays.asList(
            "set", "update", "replace", "retain", "add", "write", "insert", "push", "put", "pop", "remove", "delete", "clear"));

    public ImMut(@NotNull T value) {
        Objects.requireNonNull(value);
        this.value = freeze(value);
    }

    public static <T> T freeze(T value) {
        if (value == null) return null;

        if (Proxy.isProxyClass(value.getClass())) {
            return value;
        }
        Class<?> tClass = value.getClass();
        if (tClass.isPrimitive() || tClass.isEnum() || tClass.isAnnotation() || tClass.isArray()) return value;
        int classModifiers = tClass.getModifiers();
        if (Modifier.isFinal(classModifiers)) return value;
        if (tClass.getName().contains("$$EnhancerByCGLIB$$")) {
            return value;
        }
        Field[] declaredFields = tClass.getDeclaredFields();
        for (Field field : declaredFields) {
            int modifiers = field.getModifiers();
            if (!Modifier.isStatic(modifiers)) {
                continue;
            }
            //不可变对象不允许有公开且不final的对象字段
            if (Modifier.isPublic(modifiers) && !Modifier.isFinal(modifiers)) {
                throw new IllegalArgumentException("The field " + field.getName() + " is public and not final");
            }
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(tClass);
        enhancer.setCallback(new TargetInterceptor(value));
        Constructor<?>[] constructors = tClass.getConstructors();
        if (constructors.length == 0) {
            return value;
        }
        //创建一组无意义的参数去调用构造函数
        Optional<Constructor<?>> first = Arrays.stream(constructors).min(Comparator.comparingInt((c) -> c.getParameterTypes().length));
        Constructor<?> constructor = first.get();
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        Object[] values = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> type = parameterTypes[i];
            if (type.isPrimitive()) {
                if (type.equals(byte.class)) {
                    values[i] = 0;
                } else if (type.equals(short.class)) {
                    values[i] = 0;
                } else if (type.equals(int.class)) {
                    values[i] = 0;
                } else if (type.equals(long.class)) {
                    values[i] = 0L;
                } else if (type.equals(float.class)) {
                    values[i] = 0.0f;
                } else if (type.equals(double.class)) {
                    values[i] = 0.0;
                } else if (type.equals(char.class)) {
                    values[i] = ((char) 0);
                } else if (type.equals(boolean.class)) {
                    values[i] = false;
                }
            }
        }
        Object o = enhancer.create(parameterTypes, values);
        return ((T) o);

    }

    public static class TargetInterceptor implements MethodInterceptor {
        private final Object target;

        public TargetInterceptor(Object target) {
            this.target = target;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            int modifiers = method.getModifiers();
            if (Modifier.isStatic(modifiers)) return proxy.invokeSuper(target, args);
            String name = method.getName();
            for (String keyWord : keyWords) {
                if (name.equals(keyWord)) throw new UnsupportedOperationException("Immutable object value");
                if (name.startsWith(keyWord)) {
                    char c = name.charAt(keyWord.length());
                    if (Character.isUpperCase(c) || c == '_') {
                        throw new UnsupportedOperationException("Immutable object value");
                    }
                }
            }
            return method.invoke(target, args);
        }
    }
}
